package simx;
/**
 * 
 */
import java.io.*;
/**
 * The Instruction Fetch stage of the pipeline. Holds the PC and the IM.
 * This is the stage that creates the instructions (from within IM)
 * @author Piotr
 *
 */
public class IFStage extends PipeStage {
	
	private final int numberOfPipes;
	
	Instruction[] instr;
	
	int pc = 0;
	private IMemory im;
	private boolean branchStatus = false; //Branch status 
	private int nextNumToFetch;
	private int currentNumToFetch;
	
	/**
	 * Constructs a new IFStage initialized to initPC and with IM filled by program.
	 * @param initPC The value of the initial PC.
	 * @param program The program file to be used to fill IM.
	 * @throws FileNotFoundException 
	 */
	IFStage(int initPC, File program, int numOfPipes) throws FileNotFoundException {
		super(0);
		numberOfPipes = numOfPipes;
		instr = new Instruction[numOfPipes];
		pc = initPC;
		im = new IMemory(program);
		currentNumToFetch = numberOfPipes;
		nextNumToFetch = numberOfPipes;
	}
	
	int cycle(PipeStage prevStage) {
		//System.out.println(pc);
		//System.out.println(im.fetch(pc));
		if (stalled) {
			stalled = false;
		} else {
			if (branchStatus) {
				for (int ii = 0; ii < numberOfPipes; ii++) {
					instr[ii] = null;
				}
				branchStatus = false;
				return 0;
			} else {
				int ii;
				for (ii = 0; ii < currentNumToFetch; ii++) {
					instr[ii] = im.fetch(pc++);
				}
				for (; ii < numberOfPipes; ii++) {
					instr[ii] = null;
				}
			}
			currentNumToFetch = nextNumToFetch;
			nextNumToFetch = numberOfPipes;
		}
		int instCnt = 0;
		for (int ii = 0; ii < numberOfPipes; ii++) {
			if (instr[ii] != null && !instr[ii].isNop()) {
				instCnt++;
			}
		}
		return instCnt;
	}
	
	/**
	 * Returns true if the pc has passed the last instruction in the program. DOES NOT check if the
	 * pipeline has emptied; a branch may change the pc and continue program execution.
	 * @return true if the pc > the address of the last instruction in the IM.
	 */
	boolean isProgramFinished() {
		if (pc > im.getHighestAddress()) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Used by IDStage to tell the IF stage to not fetch the full amount of instructions
	 * next cycle because it could not completely empty its buffer. Only affects the next
	 * cycle.
	 * @param numberToFetch	The number of instruction to fetch.
	 */
	void slowDown(int numberToFetch) {
		if (numberToFetch >= 0 && numberToFetch <= numberOfPipes) {
			nextNumToFetch = numberToFetch;
		} else {
			System.out.println("Invalid argument passed in slowDown(). Exiting.");
			System.exit(1);
		}
	}
	
	
	/**
	 * Called to have the IF stage perform a branch, given the branch target.
	 * Causes the IF stage to null out the fetched instruction on the next cycle(),
	 * introducing a bubble, and fetch the branch target on the following cycle().
	 * @param branchTarget The branch target address, absolute
	 */
	void branch(int branchTarget) {
		branchStatus = true;
		pc = branchTarget;
		
	}

	/**
	 * Returns the instruction currently in the pipe stage or a null.
	 * @param pipeID the number of the instruction to get.
	 * 			Must be between [0..2*numberOfPipes - 1]
	 * @return The instruction currently in the pipe stage, null if a bubble.
	 */
	public Instruction getInstruction(int pipeID) {
		return instr[pipeID];
	}
	
	/**
	 * Like getInstruction(), but creates bubbles during a stall. Use for passing
	 * instructions between pipe stages.
	 * @return The instruction in the pipe stage, or a null if the stage is stalled.
	 */
	public Instruction passInstruction(int pipeID) {
		if (stalled) {
			return null;
		} else if (pipeID >= 0 && pipeID < numberOfPipes) {
			return instr[pipeID];
		} else {
			return null;
		}
	}
}
